/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.audioeditor.application;

import com.audioeditor.soundmanagers.FileSoundManager;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.RotateTransition;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.geometry.Bounds;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;

/**
 *
 * @author Ahmed
 */
public class MainController implements Initializable {

    @FXML
    private TextField audioFormatArea;
    @FXML
    private ImageView playStart;
    @FXML
    private ImageView playPause;
    @FXML
    private ImageView playStop;
    @FXML
    private ImageView useEnd;
    @FXML
    private ImageView useStart;
    @FXML
    private TextField fileFormatArea;
    private Stage mainStage;
    boolean fullscreen = false;
    FileSoundManager fsm = null;
    FileChooser choser = null;
    File inputFile = null;
    File outputFile = null;
    private int startTime = 0;
    private int endTime = 0;
    Media m;
    @FXML
    private ProgressBar playProgress;
    @FXML
    private TableView<SoundFile> contentTable;
    @FXML
    private TableColumn fileNameColumn;
    @FXML
    private TableColumn startTimeColumn;
    @FXML
    private TableColumn endTimeColumn;
    @FXML
    private ImageView imageContainer;
    MediaPlayer mp = null;
    RotateTransition rotate;
    final ObservableList<SoundFile> tabledata = FXCollections.observableArrayList();

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        fileNameColumn.setCellValueFactory(
                new PropertyValueFactory<SoundFile, String>("name"));
        startTimeColumn.setCellValueFactory(
                new PropertyValueFactory<SoundFile, String>("sart"));
        endTimeColumn.setCellValueFactory(
                new PropertyValueFactory<SoundFile, String>("end"));
        contentTable.setItems(tabledata);
        contentTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);



    }

    /**
     * Open an mp3 file and read it properties.
     *
     * @param event
     */
    public void openFile(ActionEvent event) {
        if (choser == null) {
            choser = new FileChooser();
        }
        if (fsm == null) {
            fsm = new FileSoundManager();
        }
        choser.getExtensionFilters().add(new FileChooser.ExtensionFilter("MP3 files", "*.mp3"));
        inputFile = choser.showOpenDialog(mainStage);
        if (inputFile != null) {
            fsm.handleSound(inputFile);
            Map<Object, Object> props = fsm.getAudioProps();
            long num = (long) props.get("duration");
            num = num / 1000000;
            setEndTime((int) (num));

            audioFormatArea.setText("00:00:00");
            fileFormatArea.setText(getTime(num));
            tabledata.add(new SoundFile(inputFile.getName(), "00:00:00", getTime(num), inputFile, "00:00:00", getTime(num)));
            m = new Media(inputFile.toURI().toString());
            mp = new MediaPlayer(m);
            mp.currentTimeProperty().addListener(new ChangeListener<Duration>() {
                @Override
                public void changed(ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) {

                    playProgress.setProgress(newValue.toSeconds() / (endTime - startTime));
                    System.out.println("changing current time to new value=" + newValue.toString());
                }
            });

        }
    }

    /**
     * Respond to muse events and set the progress par progress according to the mouse coordinates. 
     * @param event
     */
    public void move(MouseEvent event) {
        if (event.getButton() == MouseButton.PRIMARY) {
            Bounds b1 = playProgress.getLayoutBounds();
            double mouseX = event.getSceneX();
            double percent = (((b1.getMinX() + mouseX) * 100) / b1.getMaxX());
            playProgress.setProgress((percent) / 100);
            if (mp != null) {
                mp.seek(new Duration(playProgress.getProgress() * (endTime - startTime) * 1000));
            }
            
        }
    }
/**
 * Start the media player.
 * @param event 
 */
    public void play(MouseEvent event) {

        if (mp != null) {
            if (mp.getStatus() != MediaPlayer.Status.PLAYING) {
                mp.play();
            }
        }

    }
/**
 * Respond to mouse clicks on the table and change the media and media player according to the selected item.
 * @param event 
 */
    public void handleTableClick(MouseEvent event) {
        final SoundFile sf = contentTable.getSelectionModel().getSelectedItem();
        if (sf != null) {
            if (mp != null) {
                if (mp.getStatus() != MediaPlayer.Status.STOPPED) {
                    mp.stop();
                }
                m = new Media(sf.getFile().toURI().toString());
                mp = new MediaPlayer(m);
                mp.currentTimeProperty().addListener(new ChangeListener<Duration>() {
                    @Override
                    public void changed(ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) {

                        playProgress.setProgress(newValue.toSeconds() / (getSeconds(sf.getDefaultEnd()) - getSeconds(sf.getDefaultStart())));
                        
                    }
                });
                playProgress.setProgress(0);
                fsm.handleSound(sf.getFile());
                audioFormatArea.setText(sf.getSart());
                fileFormatArea.setText(sf.getEnd());
                Map<Object, Object> props = fsm.getAudioProps();
                long num = (long) props.get("duration");
                num = num / 1000000;
                setEndTime((int) (num));

            }


        }

    }
/**
 * Pauses the media player
 * @param event 
 */
    public void pause(MouseEvent event) {

        if (mp != null) {
            if (mp.getStatus() != MediaPlayer.Status.PAUSED) {
                mp.pause();
            }
        }

    }
/**
 * Stops the media player.
 * @param event 
 */
    public void stop(MouseEvent event) {

        if (mp != null) {
            if (mp.getStatus() != MediaPlayer.Status.STOPPED) {
                mp.stop();
            }
        }

    }
/**
 * Uses the current time in the media player as the end time.
 * @param event 
 */
    public void useAsEnd(MouseEvent event) {
        int selected = contentTable.getSelectionModel().getSelectedIndex();
        SoundFile sf = null;
        if (selected != -1) {
            if (mp != null) {
                int currentTime = (int) mp.getCurrentTime().toSeconds();

                System.out.println("Setting new time from end=" + currentTime);
                //  mp.pause();
                fileFormatArea.setText(getTime(currentTime));

                sf = tabledata.get(selected);

                if (sf != null) {
                    sf.setEnd(getTime(currentTime));
                    contentTable.getColumns().get(0).setVisible(false);
                    contentTable.getColumns().get(0).setVisible(true);


                }
            }
        }

    }
/**
 * Uses the current time in the media player as the start time.
 * @param event 
 */
    public void useAsSart(MouseEvent event) {
        int selected = contentTable.getSelectionModel().getSelectedIndex();
        SoundFile sf = null;
        if (selected != -1) {
            if (mp != null) {
                int currentTime = (int) mp.getCurrentTime().toSeconds();
                System.out.println("Setting new time from start=" + currentTime);


                audioFormatArea.setText(getTime(currentTime));

                sf = contentTable.getSelectionModel().getSelectedItem();
                if (sf != null) {
                    sf.setSart(getTime(currentTime));
                    contentTable.getColumns().get(0).setVisible(false);
                    contentTable.getColumns().get(0).setVisible(true);

                }
            }
        }

    }
/**
 * Remove selected item from the table.
 * @param event 
 */
    public void remove(ActionEvent event) {
        int selected = contentTable.getSelectionModel().getSelectedIndex();

        if (selected != -1) {
            tabledata.remove(selected);
            contentTable.getColumns().get(0).setVisible(false);
            contentTable.getColumns().get(0).setVisible(true);

        }
    }
/**
 * Resets the start and end values of the selected items to the default.
 * @param event 
 */
    public void reset(ActionEvent event) {
        int selected = contentTable.getSelectionModel().getSelectedIndex();
        SoundFile sf = null;
        if (selected != -1) {
            sf = contentTable.getSelectionModel().getSelectedItem();
            sf.setSart(sf.getDefaultStart());
            sf.setEnd(sf.getDefaultEnd());
            contentTable.getColumns().get(0).setVisible(false);
            contentTable.getColumns().get(0).setVisible(true);

        }
    }
/**
 * Move selected item in the table up.
 * @param event 
 */
    public void moveUp(ActionEvent event) {
        int selected = contentTable.getSelectionModel().getSelectedIndex();

        if (selected > 0) {
            swapPositions(selected, selected - 1);

        }
    }
/**
 * Move selected item in the table down.
 * @param event 
 */
    public void moveDown(ActionEvent event) {
        int selected = contentTable.getSelectionModel().getSelectedIndex();

        if (selected < tabledata.size()) {
            swapPositions(selected, selected + 1);

        }
    }
/**
 * Closes the application.
 * @param event 
 */
    public void close(ActionEvent event) {
        mainStage.close();
    }
/**
 * Show about dialog.
 * @param event 
 */
    public void showAbout(ActionEvent event) {
        Parent alert = null;
        final FXMLLoader loadr = new FXMLLoader(MainController.class.getResource("About.fxml"));
        try {
            alert = (Parent) loadr.load();
        } catch (IOException ex) {
            Logger.getLogger(MainController.class.getName()).log(Level.SEVERE, null, ex);
        }

        final Stage dialog = new Stage();

        dialog.setTitle("About Free Audio Editor");
        dialog.initStyle(StageStyle.UTILITY);

        Scene scene1 = new Scene(alert);
        dialog.setScene(scene1);
        dialog.show();
    }
/**
 * Remove all items from the table.
 * @param event 
 */
    public void removeAll(ActionEvent event) {
        tabledata.removeAll();
        contentTable.getColumns().get(0).setVisible(false);
        contentTable.getColumns().get(0).setVisible(true);
    }
/**
 * Save the selected item in the table into a new file with the new start and end time.
 * @param event 
 */
    public void saveFile(ActionEvent event) {
        if (choser == null) {
            choser = new FileChooser();
        }
        if (fsm == null) {
            fsm = new FileSoundManager();
        }
        int selected = contentTable.getSelectionModel().getSelectedIndex();
        SoundFile sf = null;
        if (selected != -1) {
            outputFile = choser.showSaveDialog(mainStage);
            if (outputFile != null) {
                sf = contentTable.getSelectionModel().getSelectedItem();
                final File selectedFile = sf.getFile();

                final int startSecs = getSeconds(sf.getSart());
                final int endSecs = getSeconds(sf.getEnd());
                System.out.print(startSecs + "::" + endSecs);
                final Task t = new Task() {
                    @Override
                    protected Object call() throws Exception {
                        fsm.handleSound(selectedFile);
                        return fsm.saveAudio(outputFile, startSecs, endSecs);
                    }
                };
                t.stateProperty().addListener(new ChangeListener() {
                    @Override
                    public void changed(ObservableValue observable, Object oldValue, Object newValue) {
                        if (newValue.equals(Worker.State.SUCCEEDED)) {
                            imageContainer.setImage(new Image(getClass().getResourceAsStream("success.png")));
                        } else if (newValue.equals(Worker.State.FAILED)) {
                            // always log your system errors with stack trace (please!!!)
                            imageContainer.setImage(new Image(getClass().getResourceAsStream("fail.png")));
                            Throwable exception = t.getException();
                            System.err.println("Error:" + exception.getLocalizedMessage());
                        }

                    }
                });
                startAnimation();
                new Thread(t).start();
            }
        }
    }
/**
 * Save the selected items into a single file one after the other according to their order in the table.
 * @param event 
 */
    public void saveMergedFile(ActionEvent event) {
        if (choser == null) {
            choser = new FileChooser();
        }
        if (fsm == null) {
            fsm = new FileSoundManager();
        }
        ObservableList<Integer> selectedIndices = contentTable.getSelectionModel().getSelectedIndices();
        int[] starts = new int[selectedIndices.size()];
        int[] ends = new int[selectedIndices.size()];
        File[] files = new File[selectedIndices.size()];
        for (int i : selectedIndices) {
            starts[i] = getSeconds(tabledata.get(i).getSart());
            ends[i] = getSeconds(tabledata.get(i).getEnd());
            files[i] = tabledata.get(i).getFile();
        }
        final int[] starts2 = starts;
        final int[] ends2 = ends;
        final File[] files2 = files;

        if (starts.length > 0) {
            outputFile = choser.showSaveDialog(mainStage);
            if (outputFile != null) {


                final Task t = new Task() {
                    @Override
                    protected Object call() throws Exception {

                        return fsm.mergeFiles(starts2, ends2, files2, outputFile);
                    }
                };
                t.stateProperty().addListener(new ChangeListener() {
                    @Override
                    public void changed(ObservableValue observable, Object oldValue, Object newValue) {
                        if (newValue.equals(Worker.State.SUCCEEDED)) {
                            imageContainer.setImage(new Image(getClass().getResourceAsStream("success.png")));
                        } else if (newValue.equals(Worker.State.FAILED)) {
                            // always log your system errors with stack trace (please!!!)
                            imageContainer.setImage(new Image(getClass().getResourceAsStream("fail.png")));
                            Throwable exception = t.getException();
                            System.err.println("Error:" + exception.getLocalizedMessage());
                        }

                    }
                });
                startAnimation();
                new Thread(t).start();
            }
        }
    }

    /**
     * @return the mainStage
     */
    public Stage getMainStage() {
        return mainStage;
    }

    /**
     * @param mainStage the mainStage to set
     */
    public void setMainStage(Stage mainStage) {
        this.mainStage = mainStage;

    }
/**
 * Gets the total seconds from a hh:mm:ss formated string.
 * @param text
 * @return 
 */
    private int getSeconds(String text) {
        int secs = 0;
        String[] data = text.split(":");
        for (int i = 0; i < data.length; i++) {
            if (i == 0) {
                secs += Integer.parseInt(data[i]) * 3600;
            } else if (i == 1) {
                secs += Integer.parseInt(data[i]) * 60;
            } else {
                secs += Integer.parseInt(data[i]);
            }
        }
        return secs;
    }
/**
 * make a hh:mm:ss formated string from a long value representing the total seconds.
 * @param num
 * @return 
 */
    private String getTime(long num) {

        int hrs = (int) num / (3600);
        double reminder = num % (3600);
        int mins = (int) reminder / (60);
        int secs = (int) (reminder % (60));
        return (hrs + ":" + mins + ":" + secs);
    }

    /**
     * @return the startTime
     */
    public int getStartTime() {
        return startTime;
    }

    /**
     * @param startTime the startTime to set
     */
    public void setStartTime(int startTime) {
        this.startTime = startTime;
    }

    /**
     * @return the endTime
     */
    public int getEndTime() {
        return endTime;
    }

    /**
     * @param endTime the endTime to set
     */
    public void setEndTime(int endTime) {
        this.endTime = endTime;
    }

    private void startAnimation() {
        imageContainer.setImage(new Image(getClass().getResourceAsStream("loading.gif")));
    }
/**
 * Swaps the positions of two items in the table.
 * @param selected index of the first 
 * @param i 
 */
    private void swapPositions(int selected1, int selected2) {
        SoundFile sf1 = tabledata.get(selected1);
        SoundFile sf2 = tabledata.get(selected2);
        tabledata.set(selected2, sf1);
        tabledata.set(selected1, sf2);
        contentTable.getColumns().get(0).setVisible(false);
        contentTable.getColumns().get(0).setVisible(true);
    }
}
